//===--- DerivedConformanceRawRepresentable.cpp - Derived RawRepresentable ===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file implements implicit derivation of the RawRepresentable protocol
//  for an enum.
//
//===----------------------------------------------------------------------===//

#include "polarphp/sema/internal/TypeChecker.h"
#include "polarphp/sema/internal/DerivedConformances.h"
#include "polarphp/ast/Decl.h"
#include "polarphp/ast/Stmt.h"
#include "polarphp/ast/Expr.h"
#include "polarphp/ast/Pattern.h"
#include "polarphp/ast/ParameterList.h"
#include "polarphp/ast/Types.h"
#include "llvm/ADT/APInt.h"

using namespace polar;

static LiteralExpr *cloneRawLiteralExpr(AstContext &C, LiteralExpr *expr) {
   LiteralExpr *clone;
   if (auto intLit = dyn_cast<IntegerLiteralExpr>(expr)) {
      clone = new (C) IntegerLiteralExpr(intLit->getDigitsText(), expr->getLoc(),
         /*implicit*/ true);
      if (intLit->isNegative())
         cast<IntegerLiteralExpr>(clone)->setNegative(expr->getLoc());
   } else if (isa<NilLiteralExpr>(expr)) {
      clone = new (C) NilLiteralExpr(expr->getLoc());
   } else if (auto stringLit = dyn_cast<StringLiteralExpr>(expr)) {
      clone = new (C) StringLiteralExpr(stringLit->getValue(), expr->getLoc());
   } else if (auto floatLit = dyn_cast<FloatLiteralExpr>(expr)) {
      clone = new (C) FloatLiteralExpr(floatLit->getDigitsText(), expr->getLoc(),
         /*implicit*/ true);
      if (floatLit->isNegative())
         cast<FloatLiteralExpr>(clone)->setNegative(expr->getLoc());
   } else if (auto boolLit = dyn_cast<BooleanLiteralExpr>(expr)) {
      clone = new (C) BooleanLiteralExpr(boolLit->getValue(), expr->getLoc(),
         /*implicit*/true);
   } else {
      llvm_unreachable("invalid raw literal expr");
   }
   clone->setImplicit();
   return clone;
}

static Type deriveRawRepresentable_Raw(DerivedConformance &derived) {
   // enum SomeEnum : SomeType {
   //   @derived
   //   typealias Raw = SomeType
   // }
   auto rawInterfaceType = cast<EnumDecl>(derived.Nominal)->getRawType();
   return derived.getConformanceContext()->mapTypeIntoContext(rawInterfaceType);
}

static std::pair<BraceStmt *, bool>
deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl, void *) {
   // enum SomeEnum : SomeType {
   //   case A = 111, B = 222
   //   @derived
   //   var raw: SomeType {
   //     switch self {
   //     case A:
   //       return 111
   //     case B:
   //       return 222
   //     }
   //   }
   // }

   auto parentDC = toRawDecl->getDeclContext();
   AstContext &C = parentDC->getAstContext();

   auto enumDecl = parentDC->getSelfEnumDecl();

   Type rawTy = enumDecl->getRawType();
   assert(rawTy);
   rawTy = toRawDecl->mapTypeIntoContext(rawTy);

//   if (enumDecl->isObjC()) {
//      // Special case: ObjC enums are represented by their raw value, so just use
//      // a bitcast.
//
//      // return unsafeBitCast(self, to: RawType.self)
//      DeclName name(C, C.getIdentifier("unsafeBitCast"), {Identifier(), C.Id_to});
//      auto functionRef = new (C) UnresolvedDeclRefExpr(name,
//                                                       DeclRefKind::Ordinary,
//                                                       DeclNameLoc());
//      auto selfRef = DerivedConformance::createSelfDeclRef(toRawDecl);
//      auto bareTypeExpr = TypeExpr::createImplicit(rawTy, C);
//      auto typeExpr = new (C) DotSelfExpr(bareTypeExpr, SourceLoc(), SourceLoc());
//      auto call = CallExpr::createImplicit(C, functionRef, {selfRef, typeExpr},
//                                           {Identifier(), C.Id_to});
//      auto returnStmt = new (C) ReturnStmt(SourceLoc(), call);
//      auto body = BraceStmt::create(C, SourceLoc(), AstNode(returnStmt),
//                                    SourceLoc());
//      return { body, /*isTypeChecked=*/false };
//   }

   Type enumType = parentDC->getDeclaredTypeInContext();

   SmallVector<AstNode, 4> cases;
   for (auto elt : enumDecl->getAllElements()) {
      auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType),
                                            SourceLoc(), SourceLoc(),
                                            Identifier(), elt, nullptr);
      pat->setImplicit();

      auto labelItem = CaseLabelItem(pat);

      auto returnExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
      auto returnStmt = new (C) ReturnStmt(SourceLoc(), returnExpr);

      auto body = BraceStmt::create(C, SourceLoc(),
                                    AstNode(returnStmt), SourceLoc());

      cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, SourceLoc(),
                                       SourceLoc(), body,
         /*case body var decls*/ None));
   }

   auto selfRef = DerivedConformance::createSelfDeclRef(toRawDecl);
   auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), selfRef,
                                        SourceLoc(), cases, SourceLoc(), C);
   auto body = BraceStmt::create(C, SourceLoc(),
                                 AstNode(switchStmt),
                                 SourceLoc());
   return { body, /*isTypeChecked=*/false };
}

static void maybeMarkAsInlinable(DerivedConformance &derived,
                                 AbstractFunctionDecl *afd) {
   AstContext &C = derived.Context;
   auto parentDC = derived.getConformanceContext();
   if (!parentDC->getParentModule()->isResilient()) {
      AccessScope access =
         afd->getFormalAccessScope(nullptr,
            /*treatUsableFromInlineAsPublic*/true);
      if (auto *attr = afd->getAttrs().getAttribute<UsableFromInlineAttr>())
         attr->setInvalid();
      if (access.isPublic())
         afd->getAttrs().add(new (C) InlinableAttr(/*implicit*/false));
   }
}

static VarDecl *deriveRawRepresentable_raw(DerivedConformance &derived) {
   AstContext &C = derived.Context;

   auto enumDecl = cast<EnumDecl>(derived.Nominal);
   auto parentDC = derived.getConformanceContext();
   auto rawInterfaceType = enumDecl->getRawType();
   auto rawType = parentDC->mapTypeIntoContext(rawInterfaceType);

   // Define the property.
   VarDecl *propDecl;
   PatternBindingDecl *pbDecl;
   std::tie(propDecl, pbDecl) = derived.declareDerivedProperty(
      C.Id_rawValue, rawInterfaceType, rawType, /*isStatic=*/false,
      /*isFinal=*/false);

   // Define the getter.
   auto getterDecl = DerivedConformance::addGetterToReadOnlyDerivedProperty(
      propDecl, rawType);
   getterDecl->setBodySynthesizer(&deriveBodyRawRepresentable_raw);

   // If the containing module is not resilient, make sure clients can get at
   // the raw value without function call overhead.
   maybeMarkAsInlinable(derived, getterDecl);

   derived.addMembersToConformanceContext({propDecl, pbDecl});

   return propDecl;
}

/// Contains information needed to synthesize a runtime version check.
struct RuntimeVersionCheck {
   PlatformKind Platform;
   llvm::VersionTuple Version;

   RuntimeVersionCheck(PlatformKind Platform, llvm::VersionTuple Version)
      : Platform(Platform), Version(Version)
   { }

   VersionRange getVersionRange() const {
      return VersionRange::allGTE(Version);
   }

   /// Synthesizes a statement which returns nil if the runtime version check
   /// fails, e.g. "guard #available(iOS 10, *) else { return nil }".
   Stmt *createEarlyReturnStmt(AstContext &C) const {
      // platformSpec = "\(attr.platform) \(attr.introduced)"
      auto platformSpec = new (C) PlatformVersionConstraintAvailabilitySpec(
         Platform, SourceLoc(),
         Version, SourceLoc()
      );

      // otherSpec = "*"
      auto otherSpec = new (C) OtherPlatformAvailabilitySpec(SourceLoc());

      // availableInfo = "#available(\(platformSpec), \(otherSpec))"
      auto availableInfo = PoundAvailableInfo::create(
         C, SourceLoc(), { platformSpec, otherSpec }, SourceLoc());

      // This won't be filled in by TypeCheckAvailability because we have
      // invalid SourceLocs in this area of the AST.
      availableInfo->setAvailableRange(getVersionRange());

      // earlyReturnBody = "{ return nil }"
      auto earlyReturn = new (C) FailStmt(SourceLoc(), SourceLoc());
      auto earlyReturnBody = BraceStmt::create(C, SourceLoc(),
                                               AstNode(earlyReturn),
                                               SourceLoc(), /*implicit=*/true);

      // guardStmt = "guard \(availableInfo) else \(earlyReturnBody)"
      StmtConditionElement conds[1] = { availableInfo };
      auto guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conds),
                                         earlyReturnBody, /*implicit=*/true);

      return guardStmt;
   }
};

/// Checks if the case will be available at runtime given the current target
/// platform. If it will never be available, returns false. If it will always
/// be available, returns true. If it will sometimes be available, adds
/// information about the runtime check needed to ensure it is available to
/// \c versionCheck and returns true.
static bool checkAvailability(const EnumElementDecl* elt, AstContext &C,
                              Optional<RuntimeVersionCheck> &versionCheck) {
   auto *attr = elt->getAttrs().getPotentiallyUnavailable(C);

   // Is it always available?
   if (!attr)
      return true;

   AvailableVersionComparison availability = attr->getVersionAvailability(C);

   assert(availability != AvailableVersionComparison::Available &&
          "DeclAttributes::getPotentiallyUnavailable() shouldn't "
          "return an available attribute");

   // Is it never available?
   if (availability != AvailableVersionComparison::PotentiallyUnavailable)
      return false;

   // It's conditionally available; create a version constraint and return true.
   assert(attr->getPlatformAgnosticAvailability() ==
          PlatformAgnosticAvailabilityKind::None &&
          "can only express #available(somePlatform version) checks");
   versionCheck.emplace(attr->Platform, *attr->Introduced);

   return true;
}

static std::pair<BraceStmt *, bool>
deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
   // enum SomeEnum : SomeType {
   //   case A = 111, B = 222
   //   @available(iOS 10, *) case C = 333
   //   @derived
   //   init?(rawValue: SomeType) {
   //     switch rawValue {
   //     case 111:
   //       self = .A
   //     case 222:
   //       self = .B
   //     case 333:
   //       guard #available(iOS 10, *) else { return nil }
   //       self = .C
   //     default:
   //       return nil
   //     }
   //   }
   // }

   auto parentDC = initDecl->getDeclContext();
   AstContext &C = parentDC->getAstContext();

   auto nominalTypeDecl = parentDC->getSelfNominalTypeDecl();
   auto enumDecl = cast<EnumDecl>(nominalTypeDecl);

   Type rawTy = enumDecl->getRawType();
   assert(rawTy);
   rawTy = initDecl->mapTypeIntoContext(rawTy);

   bool isStringEnum =
      (rawTy->getNominalOrBoundGenericNominal() == C.getStringDecl());
   llvm::SmallVector<Expr *, 16> stringExprs;

   Type enumType = parentDC->getDeclaredTypeInContext();

   auto selfDecl = cast<ConstructorDecl>(initDecl)->getImplicitSelfDecl();

   SmallVector<AstNode, 4> cases;
   unsigned Idx = 0;
   for (auto elt : enumDecl->getAllElements()) {
      // First, check case availability. If the case will definitely be
      // unavailable, skip it. If it might be unavailable at runtime, save
      // information about that check in versionCheck and keep processing this
      // element.
      Optional<RuntimeVersionCheck> versionCheck(None);
      if (!checkAvailability(elt, C, versionCheck))
         continue;

      // litPat = elt.rawValueExpr as a pattern
      LiteralExpr *litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
      if (isStringEnum) {
         // In case of a string enum we are calling the _findStringSwitchCase
         // function from the library and switching on the returned Int value.
         stringExprs.push_back(litExpr);
         litExpr = IntegerLiteralExpr::createFromUnsigned(C, Idx);
      }
      auto litPat = new (C) ExprPattern(litExpr, /*isResolved*/ true,
                                        nullptr, nullptr);
      litPat->setImplicit();

      /// Statements in the body of this case.
      SmallVector<AstNode, 2> stmts;

      // If checkAvailability() discovered we need a runtime version check,
      // add it now.
      if (versionCheck.hasValue())
         stmts.push_back(AstNode(versionCheck->createEarlyReturnStmt(C)));

      // Create a statement which assigns the case to self.

      // valueExpr = "\(enumType).\(elt)"
      auto eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true);
      auto metaTyRef = TypeExpr::createImplicit(enumType, C);
      auto valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);

      // assignment = "self = \(valueExpr)"
      auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
         /*implicit*/true,
                                         AccessSemantics::DirectToStorage);
      auto assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr,
         /*implicit*/ true);

      stmts.push_back(AstNode(assignment));

      // body = "{ \(stmts) }" (the braces are silent)
      auto body = BraceStmt::create(C, SourceLoc(),
                                    stmts, SourceLoc());

      // cases.append("case \(litPat): \(body)")
      cases.push_back(CaseStmt::create(C, SourceLoc(), CaseLabelItem(litPat),
                                       SourceLoc(), SourceLoc(), body,
         /*case body var decls*/ None));
      Idx++;
   }

   auto anyPat = new (C) AnyPattern(SourceLoc());
   anyPat->setImplicit();
   auto dfltLabelItem = CaseLabelItem::getDefault(anyPat);

   auto dfltReturnStmt = new (C) FailStmt(SourceLoc(), SourceLoc());
   auto dfltBody = BraceStmt::create(C, SourceLoc(),
                                     AstNode(dfltReturnStmt), SourceLoc());
   cases.push_back(CaseStmt::create(C, SourceLoc(), dfltLabelItem, SourceLoc(),
                                    SourceLoc(), dfltBody,
      /*case body var decls*/ None));

   auto rawDecl = initDecl->getParameters()->get(0);
   auto rawRef = new (C) DeclRefExpr(rawDecl, DeclNameLoc(), /*implicit*/true);
   Expr *switchArg = rawRef;
   if (isStringEnum) {
      // Call _findStringSwitchCase with an array of strings as argument.
      auto *Fun = new (C) UnresolvedDeclRefExpr(
         C.getIdentifier("_findStringSwitchCase"),
         DeclRefKind::Ordinary, DeclNameLoc());
      auto *strArray = ArrayExpr::create(C, SourceLoc(), stringExprs, {},
                                         SourceLoc());;
      Identifier tableId = C.getIdentifier("cases");
      Identifier strId = C.getIdentifier("string");
      auto *Args = TupleExpr::createImplicit(C, {strArray, rawRef},
                                             {tableId, strId});
      auto *CallExpr = CallExpr::create(C, Fun, Args, {}, {}, false, false);
      switchArg = CallExpr;
   }
   auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), switchArg,
                                        SourceLoc(), cases, SourceLoc(), C);
   auto body = BraceStmt::create(C, SourceLoc(),
                                 AstNode(switchStmt),
                                 SourceLoc());
   return { body, /*isTypeChecked=*/false };
}

static ConstructorDecl *
deriveRawRepresentable_init(DerivedConformance &derived) {
   AstContext &C = derived.Context;

   auto enumDecl = cast<EnumDecl>(derived.Nominal);
   auto parentDC = derived.getConformanceContext();
   auto rawInterfaceType = enumDecl->getRawType();
   auto rawType = parentDC->mapTypeIntoContext(rawInterfaceType);

   auto equatableProto = TypeChecker::getInterface(C, enumDecl->getLoc(),
                                                   KnownInterfaceKind::Equatable);
   assert(equatableProto);
   assert(
      TypeChecker::conformsToInterface(rawType, equatableProto, enumDecl, None));
   (void)equatableProto;
   (void)rawType;

   auto *rawDecl = new (C)
      ParamDecl(SourceLoc(), SourceLoc(),
                C.Id_rawValue, SourceLoc(), C.Id_rawValue, parentDC);
   rawDecl->setSpecifier(ParamSpecifier::Default);
   rawDecl->setInterfaceType(rawInterfaceType);
   rawDecl->setImplicit();
   auto paramList = ParameterList::createWithoutLoc(rawDecl);

   DeclName name(C, DeclBaseName::createConstructor(), paramList);

   auto initDecl =
      new (C) ConstructorDecl(name, SourceLoc(),
         /*Failable=*/ true, /*FailabilityLoc=*/SourceLoc(),
         /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
                              paramList,
         /*GenericParams=*/nullptr, parentDC);

   initDecl->setImplicit();
   initDecl->setBodySynthesizer(&deriveBodyRawRepresentable_init);

   initDecl->copyFormalAccessFrom(enumDecl, /*sourceIsParentContext*/true);

   // If the containing module is not resilient, make sure clients can construct
   // an instance without function call overhead.
   maybeMarkAsInlinable(derived, initDecl);

   derived.addMembersToConformanceContext({initDecl});
   return initDecl;
}

static bool canSynthesizeRawRepresentable(DerivedConformance &derived) {
   auto enumDecl = cast<EnumDecl>(derived.Nominal);

   Type rawType = enumDecl->getRawType();
   if (!rawType)
      return false;
   auto parentDC = cast<DeclContext>(derived.ConformanceDecl);
   rawType       = parentDC->mapTypeIntoContext(rawType);

   auto inherited = enumDecl->getInherited();
   if (!inherited.empty() && inherited.front().wasValidated() &&
       inherited.front().isError())
      return false;

   // The raw type must be Equatable, so that we have a suitable ~= for
   // synthesized switch statements.
   auto equatableProto =
      TypeChecker::getInterface(enumDecl->getAstContext(), enumDecl->getLoc(),
                                KnownInterfaceKind::Equatable);
   if (!equatableProto)
      return false;

   if (TypeChecker::conformsToInterface(rawType, equatableProto, enumDecl, None)
      .isInvalid())
      return false;

   // There must be enum elements.
   if (enumDecl->getAllElements().empty())
      return false;

   // Have the type-checker validate that:
   // - the enum elements all have the same type
   // - they all match the enum type
   for (auto elt : enumDecl->getAllElements()) {
      // We cannot synthesize raw representable conformance for an enum with
      // cases that have a payload.
      if (elt->hasAssociatedValues())
         return false;

      if (elt->isInvalid()) {
         return false;
      }
   }

   // If it meets all of those requirements, we can synthesize RawRepresentable conformance.
   return true;
}

ValueDecl *DerivedConformance::deriveRawRepresentable(ValueDecl *requirement) {

   // We can only synthesize RawRepresentable for enums.
   if (!isa<EnumDecl>(Nominal))
      return nullptr;

   // Check other preconditions for synthesized conformance.
   if (!canSynthesizeRawRepresentable(*this))
      return nullptr;

   if (requirement->getBaseName() == Context.Id_rawValue)
      return deriveRawRepresentable_raw(*this);

   if (requirement->getBaseName() == DeclBaseName::createConstructor())
      return deriveRawRepresentable_init(*this);

   Context.Diags.diagnose(requirement->getLoc(),
                          diag::broken_raw_representable_requirement);
   return nullptr;
}

Type DerivedConformance::deriveRawRepresentable(AssociatedTypeDecl *assocType) {

   // We can only synthesize RawRepresentable for enums.
   if (!isa<EnumDecl>(Nominal))
      return nullptr;

   // Check other preconditions for synthesized conformance.
   if (!canSynthesizeRawRepresentable(*this))
      return nullptr;

   if (assocType->getName() == Context.Id_RawValue) {
      return deriveRawRepresentable_Raw(*this);
   }

   Context.Diags.diagnose(assocType->getLoc(),
                          diag::broken_raw_representable_requirement);
   return nullptr;
}
